package cn.b504.qpcr.utils;

import cn.b504.qpcr.entity.User;
import net.sf.json.JSONArray;
import net.sf.json.JsonConfig;
import org.apache.commons.io.FileUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

public class Utils {
  public static final String ENCRYPT_SHA_1 = "SHA-1";
  public static final String ENCRYPT_SHA_224 = "SHA-224";
  public static final String ENCRYPT_SHA_256 = "SHA-256";
  public static final String CLASSPATH_RESOURCE_LOCATION = "classpath:";
  public static final String QPCR_RESOURCE_LOCATION = "classpath:qpcr";

  private static final String USER_RESOURCE_LOCATION = "classpath:qpcr/users.json";
  private static final JsonConfig USER_JSON_CONFIG = new JsonConfig();

  static {
    Map<String, Class<?>> userClassMap = new HashMap<>();
    userClassMap.put("id", String.class);
    userClassMap.put("username", String.class);
    userClassMap.put("password", String.class);
    userClassMap.put("portrait", String.class);
    userClassMap.put("expires", String.class);
    userClassMap.put("role", Integer.class);
    USER_JSON_CONFIG.setClassMap(userClassMap);
  }

  public static String hash(String data) {
    return hash(data, ENCRYPT_SHA_256);
  }

  /**
   * 散列
   *
   * @param data      字符串
   * @param algorithm the name of the algorithm requested.
   *                  See the MessageDigest section in the <a href=
   *                  "{@docRoot}/../technotes/guides/security/StandardNames.html#MessageDigest">
   *                  Java Cryptography Architecture Standard Algorithm Name Documentation</a>
   *                  for information about standard algorithm names.
   * @return hash 值
   */
  public static String hash(String data, String algorithm) {
    if (data == null) {
      return "";
    }
    MessageDigest instance;
    try {
      instance = MessageDigest.getInstance(algorithm);
    } catch (NoSuchAlgorithmException e) {
      return "";
    }
    instance.update(data.getBytes());
    byte[] digest = instance.digest();
    StringBuilder sb = new StringBuilder();
    for (byte b : digest) {
      sb.append(String.format("%X", b));
    }
    return sb.toString();
  }


  @Nullable
  public static List<User> getUserList() {
    JSONArray jsonArray = getJSONArrayFromResourceLocation(USER_RESOURCE_LOCATION);
    if (jsonArray == null) {
      return null;
    }
    return JSONArray.toList(jsonArray, new User(), USER_JSON_CONFIG);
  }

  public static boolean updateUserList(List<User> userList) {
    return updateResource(USER_RESOURCE_LOCATION, userList, USER_JSON_CONFIG);
  }

  private static JSONArray getJSONArrayFromResourceLocation(String resourceLocation) {
    try {
      File file = ResourceUtils.getFile(resourceLocation);
      if (!file.exists()) {
        return null;
      }
      String json = FileUtils.readFileToString(file, "UTF-8");
      return JSONArray.fromObject(json);
    } catch (IOException e) {
      return null;
    }
  }

  private static boolean updateResource(String resourceLocation, Object object, JsonConfig jsonConfig) {
    try {
      File file = ResourceUtils.getFile(resourceLocation);
      if (!file.exists() && !file.createNewFile()) {
        return false;
      }
      FileUtils.writeByteArrayToFile(file, JSONArray.fromObject(object, jsonConfig).toString().getBytes(StandardCharsets.UTF_8));
      return true;
    } catch (IOException e) {
      return false;
    }
  }

  private static boolean createResource(String dir, String fileName, Object object, JsonConfig jsonConfig) {
    try {
      File path = new File(ResourceUtils.getFile(CLASSPATH_RESOURCE_LOCATION), dir);
      if (!path.exists() && !path.mkdirs()) {
        return false;
      }
      File file = new File(path, fileName);
      if (!file.exists() && !file.createNewFile()) {
        return false;
      }
      FileUtils.writeByteArrayToFile(file, JSONArray.fromObject(object, jsonConfig).toString().getBytes(StandardCharsets.UTF_8));
      return true;
    } catch (IOException e) {
      return false;
    }
  }

  public static String padStart(String origin, int maxLength, String fillString) {
    if (origin.length() >= maxLength) {
      return origin;
    }
    StringBuilder sb = new StringBuilder();
    int len = maxLength - origin.length();
    int increaseCycle = (int) Math.ceil(len / (double) fillString.length());
    for (int i = 0; i < increaseCycle; i++) {
      sb.append(fillString);
    }
    return sb.substring(0, len) + origin;
  }

  public static void ensureFutureDone(Future<?> future) {
    while (!future.isDone()) {
      Thread.yield();
    }
  }

  public static void ensureEmptyFutureList(List<Future<?>> list) {
    while (!list.isEmpty()) {
      Thread.yield();
      list.removeIf(Future::isDone);
    }
  }

  public static <T> List<List<T>> init2DList(int count) {
    List<List<T>> list = new ArrayList<>();
    for (int i = 0; i < count; i++) {
      list.add(new ArrayList<>());
    }
    return list;
  }

  public static List<List<Float>> reduce(@NonNull List<List<List<Float>>> list) {
    for (int i = 1; i < list.size(); i++) {
      List<List<Float>> target = list.get(0);
      List<List<Float>> origin = list.get(i);
      for (int j = 0; j < target.size(); j++) {
        List<Float> targetData = target.get(j);
        List<Float> originData = origin.get(j);
        targetData.addAll(originData);
      }
    }
    return list.size() > 0 ? list.get(0) : new ArrayList<>();
  }

  public static List<List<Float>> reduceOrDefault(List<List<List<Float>>> list, List<List<Float>> defaultValue) {
    return list == null ? defaultValue : reduce(list);
  }
}